/* Copyright (c) 2010, mitsuhisa.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *     * Neither the name of the misuhisa.net nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <vector>
#include <cppunit/extensions/HelperMacros.h>
#include "../libnyu/MemoryManager.hpp"
#include "../libnyu/HeapObject.hpp"

using namespace nyu;

class MemoryManagerTest : public CPPUNIT_NS::TestFixture {
    CPPUNIT_TEST_SUITE(MemoryManagerTest);
    CPPUNIT_TEST(testGetInstance);
    CPPUNIT_TEST(testAllocate);
    CPPUNIT_TEST(testAllocateLarge);
    CPPUNIT_TEST(testMinorGC);
    CPPUNIT_TEST(testMajorGC);
    CPPUNIT_TEST(testAllocateMinorGC);
    CPPUNIT_TEST(testAllocateMajorGC);
    CPPUNIT_TEST_SUITE_END();

    class TestObject : public HeapObject {
        int& destructed_;

    public:
        TestObject(int& destructed) : destructed_(destructed) {
            destructed_ = 1024;
        }

        virtual ~TestObject() {
            destructed_ = 0;
        }

        static void* operator new(size_t t, void* p) {
            return p;
        }
    };

public:
    void testGetInstance() {
        MemoryManager& mm1 = MemoryManager::getInstance();
        MemoryManager& mm2 = MemoryManager::getInstance();

        CPPUNIT_ASSERT_EQUAL(&mm1, &mm2);
    }

    void testAllocate() {
        MemoryManager& mm = MemoryManager::getInstance();
        int destructed;
        TestObject* o = new(mm.allocate(sizeof(TestObject))) TestObject(destructed);

        CPPUNIT_ASSERT(o);
        CPPUNIT_ASSERT_EQUAL(o->refcnt(), size_t(1));
        CPPUNIT_ASSERT(!o->isAlloced());

        // check private member
        CPPUNIT_ASSERT_EQUAL(*(reinterpret_cast<size_t*>(reinterpret_cast<char*>(o) + sizeof(void*))), size_t(sizeof(TestObject)));

        CPPUNIT_ASSERT_EQUAL(mm.getTotalAllocated(), size_t(sizeof(TestObject)));
        CPPUNIT_ASSERT_EQUAL(mm.countAlivedObjects(), size_t(1));

        CPPUNIT_ASSERT(destructed == 1024);

        o->unref();
        CPPUNIT_ASSERT_EQUAL(mm.countAlivedObjects(), size_t(0));
        CPPUNIT_ASSERT(destructed == 1024);

        mm.minorGC();
        mm.resetTotalAllocated();

        CPPUNIT_ASSERT(!destructed);
    }

    void testAllocateLarge() {
        MemoryManager& mm = MemoryManager::getInstance();
        int destructed;
        TestObject* o = new(mm.allocate(sizeof(TestObject) + 256)) TestObject(destructed);

        CPPUNIT_ASSERT(o);
        CPPUNIT_ASSERT_EQUAL(o->refcnt(), size_t(1));
        CPPUNIT_ASSERT(o->isAlloced());

        // check private member
        CPPUNIT_ASSERT_EQUAL(*(reinterpret_cast<size_t*>(reinterpret_cast<char*>(o) + sizeof(void*))), size_t(sizeof(TestObject) + 256));

        CPPUNIT_ASSERT_EQUAL(mm.getTotalAllocated(), size_t(sizeof(TestObject) + 256));
        CPPUNIT_ASSERT_EQUAL(mm.countAlivedObjects(), size_t(0));

        CPPUNIT_ASSERT(destructed == 1024);

        o->unref();
        CPPUNIT_ASSERT_EQUAL(mm.countAlivedObjects(), size_t(0));
        CPPUNIT_ASSERT(destructed == 1024);

        mm.minorGC();
        mm.resetTotalAllocated();

        CPPUNIT_ASSERT(!destructed);
    }

    void testMinorGC() {
        MemoryManager& mm = MemoryManager::getInstance();
        std::vector<int> destructeds(10);

        for (int i = 0; i < destructeds.size(); i++) {
            TestObject* o = new(mm.allocate(sizeof(TestObject))) TestObject(destructeds[i]);
            o->unref();
        }

        CPPUNIT_ASSERT_EQUAL(mm.getTotalAllocated(), size_t(sizeof(TestObject) * destructeds.size()));
        CPPUNIT_ASSERT_EQUAL(mm.countAlivedObjects(), size_t(0));

        for (int i = 0; i < destructeds.size(); i++) {
            CPPUNIT_ASSERT(destructeds[i] == 1024);
        }

        mm.minorGC();

        for (int i = 0; i < destructeds.size(); i++) {
            CPPUNIT_ASSERT(!destructeds[i]);
        }

        mm.resetTotalAllocated();
    }

    void testMajorGC() {
        MemoryManager& mm = MemoryManager::getInstance();
        std::vector<int> destructeds(10);

        for (int i = 0; i < destructeds.size(); i++) {
            TestObject* o = new(mm.allocate(sizeof(TestObject))) TestObject(destructeds[i]);
            o->unref();
        }

        CPPUNIT_ASSERT_EQUAL(mm.getTotalAllocated(), size_t(sizeof(TestObject) * destructeds.size()));
        CPPUNIT_ASSERT_EQUAL(mm.countAlivedObjects(), size_t(0));

        for (int i = 0; i < destructeds.size(); i++) {
            CPPUNIT_ASSERT(destructeds[i] == 1024);
        }

        mm.majorGC();

        for (int i = 0; i < destructeds.size(); i++) {
            CPPUNIT_ASSERT(!destructeds[i]);
        }

        mm.resetTotalAllocated();
    }

    void testAllocateMinorGC() {
        MemoryManager& mm = MemoryManager::getInstance();
        std::vector<int> destructeds(1000000);

        for (int i = 0; i < destructeds.size(); i++) {
            TestObject* o = new(mm.allocate(sizeof(TestObject))) TestObject(destructeds[i]);

            CPPUNIT_ASSERT(o);
            CPPUNIT_ASSERT_EQUAL(o->refcnt(), size_t(1));
            CPPUNIT_ASSERT(!o->isAlloced());

            o->unref();
        }

        CPPUNIT_ASSERT_EQUAL(mm.getTotalAllocated(), size_t(sizeof(TestObject) * destructeds.size()));
        CPPUNIT_ASSERT_EQUAL(mm.countAlivedObjects(), size_t(0));

        mm.minorGC();

        for (int i = 0; i < destructeds.size(); i++) {
            CPPUNIT_ASSERT(!destructeds[i]);
        }

        mm.resetTotalAllocated();
    }

    void testAllocateMajorGC() {
        MemoryManager& mm = MemoryManager::getInstance();
        std::vector<int> destructeds(100000);

        for (int i = 0; i < 10; i++) {
            std::vector<TestObject*> oo(destructeds.size() / 10);
            for (int j = 0; j < oo.size(); j++) {
                TestObject* o = new(mm.allocate(sizeof(TestObject))) TestObject(destructeds[i * oo.size() + j]);

                CPPUNIT_ASSERT(o);
                CPPUNIT_ASSERT_EQUAL(o->refcnt(), size_t(1));
                CPPUNIT_ASSERT(!o->isAlloced());

                oo[j] = o;

                for (int k = 0; k < j; k++) {
                    CPPUNIT_ASSERT_EQUAL(oo[k]->refcnt(), size_t(1));
                }
            }

            CPPUNIT_ASSERT_EQUAL(mm.countAlivedObjects(), oo.size());

            for (int j = 0; j < oo.size(); j++) {
                oo[j]->unref();
            }

            CPPUNIT_ASSERT_EQUAL(mm.countAlivedObjects(), size_t(0));
        }

        CPPUNIT_ASSERT_EQUAL(mm.getTotalAllocated(), size_t(sizeof(TestObject) * destructeds.size()));
        CPPUNIT_ASSERT_EQUAL(mm.countAlivedObjects(), size_t(0));

        mm.majorGC();

        for (int i = 0; i < destructeds.size(); i++) {
            CPPUNIT_ASSERT(!destructeds[i]);
        }

        mm.resetTotalAllocated();
    }
};

CPPUNIT_TEST_SUITE_REGISTRATION(MemoryManagerTest);
